package kino.net;

import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.DecoderException;
import io.netty.handler.codec.ReplayingDecoder;
import io.netty.util.AttributeKey;
import kino.client.KinoClient;
import kino.server.KinoServer;

import java.util.List;

/**
 * 命令节码器
 */
public class CommandDecoder extends ByteToMessageDecoder {

    static final char CR = '\r';
    static final char LF = '\n';

    public CommandDecoder(){
    }
    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> readyFrameList) throws Exception {
        KinoServer kinoServer = (KinoServer) channelHandlerContext.attr(AttributeKey.valueOf("kinoServer")).get();
        Channel channel = channelHandlerContext.channel();
        KinoClient client = kinoServer.getKinoClientByChannel(channel);
        // 解码 将原请求转换成帧，该帧包含
        // 原始命令文本 、 解析后的命令 与 参数
        RedisFrame frame = doDecode(channelHandlerContext, byteBuf, readyFrameList, client);
        // 把帧加入待处理帧列表（readyFrameList）中，在下一个handler（KinoServerHandler）中进行逐个执行
        if (frame != null && frame.getState() == 1){
            readyFrameList.add(frame);
        }
    }

    private synchronized RedisFrame doDecode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> readyFrameList,KinoClient client) throws Exception {
        RedisFrame frame = null;
        int currentArgsLen = 0;
        int argsCount = 0;
        int startRIndex = byteBuf.readerIndex();
        try {
            while (frame == null || frame.getState() == 0) {
                if (KinoClient.State.NUMBER_OF_ARGS.equals(client.getState())) {
                    // （初始状态）以 * 开头，跟一个数字代表参数个数
                    byte startByte = byteBuf.readByte();
                    if (startByte != '*') {
                        client.checkpoint(KinoClient.State.NUMBER_OF_ARGS);
                        throw new DecoderException("can not found *");
                    }
                    // 读取参数个数
                    argsCount = parseRedisNumber(byteBuf, client);
                    frame = new RedisFrame(argsCount);
                    // 切换到 （参数1 长度读取状态）
                    client.checkpoint(KinoClient.State.NUMBER_BYTE_OF_ARGS);
                } else if (KinoClient.State.NUMBER_BYTE_OF_ARGS.equals(client.getState())) {
                    // （参数长度读取状态）以$开头 后面会跟一个数字代表这个参数的字节长度
                    if (byteBuf.readByte() != '$') {
                        // 遇到起始不是$ 则报错回滚
                        throw new DecoderException("can not found $");
                    }
                    // 读取该参数字节长度
                    currentArgsLen = parseRedisNumber(byteBuf, client);
                    client.checkpoint(KinoClient.State.ARGS_DATA);
                } else if (KinoClient.State.ARGS_DATA.equals(client.getState())) {
                    // （参数内容读取状态） 根据 （参数长度读取状态）获取的长度，一次性读取 currentArgsLen个字节为参数内容
                    frame.AppendArgs(byteBuf.readBytes(currentArgsLen).array());
                    // 结尾标识符
                    if (byteBuf.readByte() != CR || byteBuf.readByte() != LF) {
                        throw new DecoderException("can not found CR OR LF");
                    }
                    if ((--argsCount) <= 0) {
                        // 最后一个参数的内容读取完毕客户端状态回到 （初始状态）
                        client.checkpoint(KinoClient.State.NUMBER_OF_ARGS);
                        frame.setClient(client);
                        // 命令帧组装完毕
                        frame.setState(1);
                        break;
                    } else {
                        // 状态回到 （参数长度读取状态）代表着取去下一个参数
                        client.checkpoint(KinoClient.State.NUMBER_BYTE_OF_ARGS);
                    }
                } else {
                    throw new DecoderException("");
                }
            }
            if (frame != null && frame.getState() == 1) {
                // 把原命令解析放到客户端缓冲区去，记录aof
                int endRIndex = byteBuf.readerIndex();
                int capacity = endRIndex - startRIndex;
                byte[] oriBytes = new byte[capacity];
                int temp = 0;
                while (temp < endRIndex - startRIndex) {
                    oriBytes[temp] = byteBuf.getByte(startRIndex + temp);
                    temp++;
                }
                frame.setOriQueryBytes(oriBytes);
                return frame;
            }
        } catch (Exception e) {
            // 指针回调等报文
            byteBuf.readerIndex(startRIndex);
            // 回滚状态到（初始状态）
            client.checkpoint(KinoClient.State.NUMBER_OF_ARGS);
        }
        return null;
    }

    private int parseRedisNumber(ByteBuf byteBuf, KinoClient client) {
        byte readByte = byteBuf.readByte();
        boolean negative = readByte == '-';
        if (negative) {
            readByte = byteBuf.readByte();
        }
        int result = 0;
        do {
            int digit = readByte - '0';
            if (digit >= 0 && digit < 10) {
                result = (result * 10) + digit;
            } else {
                throw new DecoderException("Invalid character in integer");
            }
        } while ( (readByte = byteBuf.readByte()) != CR);

        if ((readByte = byteBuf.readByte()) != LF) {
            throw new DecoderException("can not found LF");
        }
        return (negative? -result:result);
    }

}